const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('循环引用'));
  }
  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    let called = false;
    try {
      let then = x.then;

      if (typeof then === 'function') {
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        resolve(x);
      }
    } catch (error) {
      if (called) return;
      reject(error);
    }
  } else {
    resolve(x);
  }
}

class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (value instanceof Promise) {
        return value.then(resolve, reject);
      }
      if (this.status === PENDING) {
        this.value = value;
        this.status = FULFILLED;
        this.onResolvedCallbacks.forEach((fn) => fn());
      }
    };

    const reject = (reason) => {
      if (this.status === PENDING) {
        this.reason = reason;
        this.status = REJECTED;
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : (r) => {
            throw r;
          };

    const promise2 = new Promise((resolve, reject) => {
      const callFn = (fn, v) => {
        setTimeout(() => {
          try {
            const x = fn(v);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      };
      if (this.status === FULFILLED) {
        callFn(onFulfilled, this.value);
      }
      if (this.status === REJECTED) {
        callFn(onRejected, this.reason);
      }
      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
          callFn(onFulfilled, this.value);
        });
        this.onRejectedCallbacks.push(() => {
          callFn(onRejected, this.reason);
        });
      }
    });

    return promise2;
  }
  catch(fn) {
    return this.then(null, fn);
  }
  finally(onFinally) {
    let p = this.constructor;
    return this.then(
      (v) => p.resolve(onFinally).then(() => v),
      (reason) =>
        p.resolve(onFinally).then(() => {
          throw reason;
        })
    );
  }
  static resolve(v) {
    return new Promise((r) => r(v));
  }
  static reject(r) {
    return new Promise((_, reject) => reject(r));
  }

  static all(promises) {
    return new Promise((resolve, reject) => {
      let times = 0;
      const result = Array.from(promises);
      const processSuccess = (i, data) => {
        result[i] = data;
        if (++times === promises.length) {
          resolve(result);
        }
      };
      for (let i = 0; i < promises.length; i++) {
        const p = promises[i];
        if (p && typeof p.then === 'function') {
          p.then((res) => {
            processSuccess(i, res);
          }, reject);
        } else {
          processSuccess(i, p);
        }
      }
    });
  }
  static allSettled(promises) {
    return new Promise((resolve, reject) => {
      const result = [];
      let times = 0;
      const process = (index, status, data) => {
        if (status === 'fulfilled') {
          result[index] = {
            status,
            value: data,
          };
        } else {
          result[index] = {
            status,
            reason: data,
          };
        }
        if (++times === promises.length) {
          resolve(result);
        }
      };
      for (let index = 0; index < promises.length; index++) {
        const p = promises[index];
        if (p && p.then) {
          const fn = process.bind(null, index);
          p.then(fn.bind(null, 'fulfilled'), fn.bind(null, 'rejected'));
        } else {
          process(index, 'fulfilled', p);
        }
      }
    });
  }

  static any(promises) {
    return new Promise((resolve, reject) => {
      let times = 0;
      const result = Array.from(promises);
      const processReject = (i, data) => {
        result[i] = data;
        if (++times === promises.length) {
          reject(new Error('No Promise in Promise.any was resolved'));
        }
      };
      for (let i = 0; i < promises.length; i++) {
        const p = promises[i];
        if (p && typeof p.then === 'function') {
          p.then(resolve, processReject.bind(null, i));
        } else {
          resolve(p);
        }
      }
    });
  }
}

Promise.deferred = function () {
  let dfd = {};
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
};

module.exports = Promise;
